/*******************************************************************************
 * Copyright (c) 2010 Oak Ridge National Laboratory.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 * 
 * SPDX-License-Identifier: EPL-2.0
 ******************************************************************************/
package org.eclipse.nebula.visualization.widgets.figureparts;

import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.nebula.visualization.widgets.util.GraphicsUtil;
import org.eclipse.nebula.visualization.xygraph.util.XYGraphMediaFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Pattern;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;

/**
 * A ramp looks like a colorful donut, which is used to indicate the alarm limit, hihi, hi, lo or lolo.
 * The ramp is based on a round scale which is in the same polar coordinate system as the ramp.
 * The ramp could be used for any round scale based widgets, such as meter, gauge and knob etc.
 * @author Xihui Chen
 *
 */
public class RoundScaledRamp extends Figure {

	private static final int OVERLAP_DEGREE = 2;

	/**
	 * The alarm thereshold for a PV, includs HIHI, HI, LO or LOLO. 
	 */
	public enum Threshold{
		HIHI, 
		HI,
		LO,
		LOLO		
	}
	
	private RoundScale scale;
	private ThresholdMarker lolo = new ThresholdMarker(10, XYGraphMediaFactory.COLOR_RED, true);
	private ThresholdMarker lo = new ThresholdMarker(25, XYGraphMediaFactory.COLOR_ORANGE, true);
	private ThresholdMarker hi = new ThresholdMarker(75, XYGraphMediaFactory.COLOR_ORANGE, true);
	private ThresholdMarker hihi = new ThresholdMarker(90, XYGraphMediaFactory.COLOR_RED, true);
	
	//the middle value in the normal range, for internal use only
	private ThresholdMarker normal = new ThresholdMarker(50, XYGraphMediaFactory.COLOR_GREEN, true);
	private ThresholdMarker min = new ThresholdMarker(0, null, true);	
	private ThresholdMarker max = new ThresholdMarker(100, null, true);
	private int rampWidth = 20;
	private boolean gradient = true;
	
	private boolean dirty = true;
	
	/**
	 * Constructor
	 * @param scale the round scale
	 */
	public RoundScaledRamp(RoundScale scale) {
		this.scale = scale;
	}
	
    @Override
    public void setBounds(Rectangle rect) {    
    	if(!bounds.equals(rect))
    		setDirty(true);    	
    	//get the square in the rect
    	rect.width = Math.min(rect.width, rect.height);
    	rect.height = rect.width;    	
    	super.setBounds(rect);  	
    } 

    @Override
    public Dimension getPreferredSize(int wHint, int hHint) {
    	wHint = Math.min(wHint, hHint);
		hHint = wHint;
		Dimension size = new Dimension(wHint, hHint);			
		return size;
    }
    
    /**
     * update the the position for each threshold, and other parameters related to the positions.  
     */
    private void updateThresholdPosition(){
    	if(dirty){   
    		//get normal value
    		double lowLimit;
    		double upLimit;
    		if(lo.visible)
    			lowLimit = lo.value;
    		else if(lolo.visible)
    			lowLimit = lolo.value;
    		else
    			lowLimit = scale.getRange().getLower();
    		
    		if(hi.visible)
    			upLimit = hi.value;
    		else if(hihi.visible)
    			upLimit = hihi.value;
    		else
    			upLimit = scale.getRange().getUpper();
    		
    		//update normal
    		normal.value = (lowLimit + upLimit)/2;   	
    		normal.absolutePosition = (int) scale.getCoercedValuePosition(normal.value, false);
    		normal.relativePosition = (int) scale.getCoercedValuePosition(normal.value, true);
    		normal.rightPoint = new PolarPoint(
    					bounds.width/2, (normal.absolutePosition - OVERLAP_DEGREE)*Math.PI/180).toAbsolutePoint(bounds);
    		normal.leftPoint = new PolarPoint(
    					bounds.width/2, (normal.absolutePosition + OVERLAP_DEGREE)*Math.PI/180).toAbsolutePoint(bounds);    	
    		
    		//update min, max
    		if(scale.getRange().isMinBigger()){
    			min.value = scale.getRange().getUpper();
    			max.value = scale.getRange().getLower();
    		}
    		else{
    			min.value = scale.getRange().getLower();
    			max.value = scale.getRange().getUpper();
    		}
    		min.absolutePosition = (int) scale.getCoercedValuePosition(min.value, false);
    		min.relativePosition = (int) scale.getCoercedValuePosition(min.value, true);
    		max.absolutePosition = (int) scale.getCoercedValuePosition(max.value, false);
    		max.relativePosition = (int) scale.getCoercedValuePosition(max.value, true);
    		
    		//update lolo, lo, hi, hihi
    		if(lolo.visible){
    			lolo.absolutePosition = (int) scale.getCoercedValuePosition(lolo.value, false);
    			lolo.relativePosition = (int) scale.getCoercedValuePosition(lolo.value, true);
    			lolo.rightPoint = new PolarPoint(
    					bounds.width/2, (lolo.absolutePosition - OVERLAP_DEGREE)*Math.PI/180).toAbsolutePoint(bounds);
    			lolo.leftPoint = new PolarPoint(
    					bounds.width/2, (lolo.absolutePosition + OVERLAP_DEGREE)*Math.PI/180).toAbsolutePoint(bounds);
    		}    			
    		if(lo.visible) {
    			lo.absolutePosition = (int) scale.getCoercedValuePosition(lo.value, false);
    			lo.relativePosition = (int) scale.getCoercedValuePosition(lo.value, true);
    			lo.rightPoint = new PolarPoint(
    					bounds.width/2, (lo.absolutePosition - OVERLAP_DEGREE)*Math.PI/180).toAbsolutePoint(bounds);
    			lo.leftPoint = new PolarPoint(
    					bounds.width/2, (lo.absolutePosition + OVERLAP_DEGREE)*Math.PI/180).toAbsolutePoint(bounds);
    		}    			
    		if(hi.visible){    			
    			hi.absolutePosition = (int) scale.getCoercedValuePosition(hi.value, false);
    			hi.relativePosition = (int) scale.getCoercedValuePosition(hi.value, true);
    			hi.rightPoint = new PolarPoint(
    					bounds.width/2, (hi.absolutePosition - OVERLAP_DEGREE)*Math.PI/180).toAbsolutePoint(bounds);
    			hi.leftPoint = new PolarPoint(
    					bounds.width/2, (hi.absolutePosition + OVERLAP_DEGREE)*Math.PI/180).toAbsolutePoint(bounds);
    		}    		
    		if(hihi.visible){
    			hihi.absolutePosition = (int) scale.getCoercedValuePosition(hihi.value, false);
    			hihi.relativePosition = (int) scale.getCoercedValuePosition(hihi.value, true);
    			hihi.rightPoint = new PolarPoint(
    					bounds.width/2, (hihi.absolutePosition - OVERLAP_DEGREE)*Math.PI/180).toAbsolutePoint(bounds);
    			hihi.leftPoint = new PolarPoint(
    					bounds.width/2, (hihi.absolutePosition + OVERLAP_DEGREE)*Math.PI/180).toAbsolutePoint(bounds);
    		}    	
    		setDirty(false);
    	}
    	
    }

    @Override
    protected void paintClientArea(Graphics graphics) {
    	updateThresholdPosition();
    	graphics.setAntialias(SWT.ON);
    	graphics.setLineWidth(rampWidth);
    	graphics.pushState();
    	int overlap = 0;
    	Pattern pattern = null;
    	boolean support3D = GraphicsUtil.testPatternSupported(graphics);    	
    	//draw lolo part
    	if(lolo.visible){    		
  			graphics.setBackgroundColor(lolo.color);  			
  			graphics.fillArc(bounds, lolo.absolutePosition, min.relativePosition - lolo.relativePosition);
    		
    	}
    	//draw lo part
    	if(lo.visible){
    		if(support3D && gradient && lolo.visible){
    				try {
						pattern = new Pattern(Display.getCurrent(), lolo.leftPoint.x, lolo.leftPoint.y, 
								lo.rightPoint.x, lo.rightPoint.y, lolo.color, lo.color);
						graphics.setBackgroundPattern(pattern);    		
						overlap = OVERLAP_DEGREE/2;
					} catch (Exception e) {
						support3D = false;
						pattern.dispose();
						graphics.setBackgroundColor(lo.color);
						overlap = 0;
					}
    		} else {
    			graphics.setBackgroundColor(lo.color);
    			overlap = 0;
    		}
    		
    		if(lolo.visible)
    			graphics.fillArc(bounds, lo.absolutePosition, 
    					lolo.relativePosition - lo.relativePosition + overlap);
    		else
    			graphics.fillArc(bounds, lo.absolutePosition, min.relativePosition - lo.relativePosition);
    		if(gradient && lolo.visible && support3D)
    			pattern.dispose();    		
    	}
    	
    	//draw left normal part
    	//get the left marker
    	boolean leftMarkerVisible = false;
    	ThresholdMarker leftMarker = null;
    	if(lo.visible){
    		leftMarkerVisible = true;
    		leftMarker = lo;
    	} else if (lolo.visible){
    		leftMarkerVisible =true;
    		leftMarker = lolo;    		
    	} else 
    		leftMarkerVisible = false;
    	
    	if(gradient && leftMarkerVisible && support3D){
    		pattern = new Pattern(Display.getCurrent(), leftMarker.leftPoint.x, leftMarker.leftPoint.y, 
    				normal.rightPoint.x, normal.rightPoint.y, leftMarker.color, normal.color);
    		graphics.setBackgroundPattern(pattern);    		
    		overlap = OVERLAP_DEGREE/2;
    	} else {
    		graphics.setBackgroundColor(normal.color);
    		overlap = 0;
    	}
    		
    	if(leftMarkerVisible)
    		graphics.fillArc(bounds, normal.absolutePosition, 
    				leftMarker.relativePosition - normal.relativePosition + overlap);
    	else
    		graphics.fillArc(bounds, normal.absolutePosition, min.relativePosition - normal.relativePosition);
    	
    	if(gradient && leftMarkerVisible && support3D)
    		pattern.dispose();   		
    	
    	//draw right normal part
    	//get the right marker
    	boolean rightMarkerVisible = false;
    	ThresholdMarker rightMarker = null;
    	if(hi.visible){
    		rightMarkerVisible = true;
    		rightMarker = hi;
    	} else if (hihi.visible){
    		rightMarkerVisible =true;
    		rightMarker = hihi;    		
    	} else 
    		rightMarkerVisible = false;
    	
    	if(gradient && rightMarkerVisible && support3D){
    		pattern = new Pattern(Display.getCurrent(), rightMarker.rightPoint.x, rightMarker.rightPoint.y, 
    				normal.leftPoint.x, normal.leftPoint.y, rightMarker.color, normal.color);
    		graphics.setBackgroundPattern(pattern);    		
    		overlap = OVERLAP_DEGREE/2;
    	} else {
    		graphics.setBackgroundColor(normal.color);
    		overlap = 0;
    	}
    		
    	if(rightMarkerVisible)
    		graphics.fillArc(bounds, rightMarker.absolutePosition, 
    				normal.relativePosition - rightMarker.relativePosition + overlap + 1);
    	else
    		graphics.fillArc(bounds, max.absolutePosition, 
    				normal.relativePosition - max.relativePosition +1);
    	
    	if(gradient && rightMarkerVisible && support3D)
    		pattern.dispose();
    	
    	
    	//draw hi part
    	if(hi.visible){
    		if(hihi.visible){
	    		rightMarkerVisible = true;
	    		rightMarker = hihi;   	
	    	} else 
	    		rightMarkerVisible = false;
	    	
	    	if(gradient && rightMarkerVisible && support3D){
	    		pattern = new Pattern(Display.getCurrent(), rightMarker.rightPoint.x, rightMarker.rightPoint.y, 
	    				hi.leftPoint.x, hi.leftPoint.y, rightMarker.color, hi.color);
	    		graphics.setBackgroundPattern(pattern);    		
	    		overlap = OVERLAP_DEGREE/2;
	    	} else {
	    		graphics.setBackgroundColor(hi.color);
	    		overlap = 0;
	    	}
	    		
	    	if(rightMarkerVisible)
	    		graphics.fillArc(bounds, rightMarker.absolutePosition, 
	    				hi.relativePosition - rightMarker.relativePosition + overlap);
	    	else
	    		graphics.fillArc(bounds, max.absolutePosition, 
	    				hi.relativePosition - max.relativePosition);
	    	
	    	if(gradient && rightMarkerVisible && support3D)
	    		pattern.dispose();
    	}
    	
    	
    	//draw hihi part
    	if(hihi.visible){
    		if(gradient && support3D)
    			overlap = OVERLAP_DEGREE/2;
    		else
    			overlap = 0;
    		graphics.setBackgroundColor(hihi.color);
    		graphics.fillArc(bounds, max.absolutePosition, 
    				hihi.relativePosition - max.relativePosition + overlap);
    	}
    	
    	graphics.popState();    	
    	graphics.fillOval(bounds.x + rampWidth, bounds.y + rampWidth, 
    			bounds.width-2*rampWidth,bounds.height - 2*rampWidth);
    	
    	super.paintClientArea(graphics);    	
    }
    
	
	
	/**
	 * @return the round scale for this ramp
	 */
	public RoundScale getScale() {
		return scale;
	}
	/**
	 * @param scale the round scale to set
	 */
	public void setScale(RoundScale scale) {
		this.scale = scale;
		setDirty(true);
	}
	/**
	 * @return the rampWidth
	 */
	public int getRampWidth() {
		return rampWidth;
	}
	/**
	 * @param rampWidth the rampWidth to set
	 */
	public void setRampWidth(int rampWidth) {
		this.rampWidth = rampWidth;
		setDirty(true);
	}
	
	
	/**
	 * If gradient is true, the color will be displayed in gradient style
	 * @param gradient the gradient to set
	 */
	public void setGradient(boolean gradient) {
		this.gradient = gradient;
		setDirty(true);
	}
	
	/**
	 * Set value of the threshold.
	 * @param thresholdName the threshold name which should be one of {@link Threshold}
	 * @param value the value to set
	 */
	public void setThresholdValue(Threshold thresholdName, double value){
		switch (thresholdName) {
		case HIHI:
			hihi.value = value;
			break;
		case HI:
			hi.value =value;
			break;
		case LO:
			lo.value = value;
			break;
		case LOLO:
			lolo.value = value;			
		default:
			break;
		}
		setDirty(true);
	}


	/**
	 * Set color of the threshold.
	 * @param thresholdName the threshold name which should be one of {@link Threshold}
	 * @param color the RGB color to set
	 */
	public void setThresholdColor(Threshold thresholdName, RGB color){
		switch (thresholdName) {
		case HIHI:
			hihi.setColor(color);
			break;
		case HI:
			hi.setColor(color);
			break;
		case LO:
			lo.setColor(color);
			break;
		case LOLO:
			lolo.setColor(color);			
		default:
			break;
		}
	}
	
	
	/**
	 * Set visibility of the threshold.
	 * @param thresholdName the threshold name which should be one of {@link Threshold}
	 * @param visible true if this threshold should be visible
	 */
	public void setThresholdVisibility(Threshold thresholdName, boolean visible){
		switch (thresholdName) {
		case HIHI:
			hihi.visible = visible;
			break;
		case HI:
			hi.visible =visible;
			break;
		case LO:
			lo.visible = visible;
			break;
		case LOLO:
			lolo.visible = visible;			
		default:
			break;
		}
		setDirty(true);
	}
	
	
	
	/**
	 * @param dirty the dirty to set
	 */
	public void setDirty(boolean dirty) {
		this.dirty = dirty;
	}

	/**
	 * Hold the properties for each threshold.
	 * @author Xihui Chen
	 */
	static class ThresholdMarker {	
		
		private double value;		
		private Color color;		
		private boolean visible;
		
		/** Its  absolute degree position on the scale */
		private int absolutePosition;
		
		/** Its relative degree position on the scale */
		private int relativePosition;
		
		/** The right overlap point. Only used for gradient */
		private Point rightPoint;
		
		/** The left overlap point. Only used for gradient */
		private Point leftPoint;
		
		/**
		 * @param value
		 * @param color
		 * @param visible
		 */
		public ThresholdMarker(double value, RGB color, boolean visible) {
			this.value = value;
			if(color != null)
				this.color = XYGraphMediaFactory.getInstance().getColor(color);
			this.visible = visible;
		}

		/**
		 * @param color the RGB color to set
		 */
		public void setColor(RGB color) {
			this.color = XYGraphMediaFactory.getInstance().getColor(color);
		}

	
}

}
